// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <iomanip>
#include <sstream>
#include <string>

#include "coin/feed/fastfeed/feed.pb.h"
#include "coin2/exchange/base/feed_util/open_interest.h"

struct TopLongShortAccountRatio {
  int64_t timestamp;
  double long_short_ratio;
  double long_account;
  double short_account;

  void Clear() {
    timestamp = 0;
    long_short_ratio = 0;
    long_account = 0;
    short_account = 0;
  }
};

struct TopLongShortPositionRatio {
  int64_t timestamp;
  double long_short_ratio;
  double long_position;
  double short_position;

  void Clear() {
    timestamp = 0;
    long_short_ratio = 0;
    long_position = 0;
    short_position = 0;
  }
};

struct GlobalLongShortAccountRatio {
  int64_t timestamp;
  double long_short_ratio;
  double long_account;
  double short_account;

  void Clear() {
    timestamp = 0;
    long_short_ratio = 0;
    long_account = 0;
    short_account = 0;
  }
};

struct TakerBuySellRatio {
  int64_t timestamp;
  double buy_sell_ratio;
  double buy_volume;
  double sell_volume;

  void Clear() {
    timestamp = 0;
    buy_sell_ratio = 0;
    buy_volume = 0;
    sell_volume = 0;
  }
};

template <typename T>
class TradingData {
 public:
  enum TradingDataOption { SNAPSHOT, DELTA };

  using Iterator = typename std::deque<T>::const_iterator;
  using ReverseIterator = typename std::deque<T>::const_reverse_iterator;

  // get freezed
  std::optional<const T*> Get(unsigned int idx = 0) const {
    unsigned int real_index = idx;
    if (has_unfreezed_) real_index += 1;

    if (data_.size() <= real_index) {
      return std::nullopt;
    } else {
      return std::optional(&(data_.at(real_index)));
    }
  }

  int64_t Timestamp() const { return timestamp_; }
  void SetTimestamp(int64_t timestamp) {
    timestamp_ = timestamp;
  }

  fastfeed::proto::TradingDataInterval Interval() const { return interval_; }
  void SetInterval(fastfeed::proto::TradingDataInterval interval) {
    interval_ = interval;
  }

  // for freezed
  T* Add() {
    has_unfreezed_ = false;
    AddEmtpyItem();
    return (&data_.front());
  }

  // for unfreezed
  std::optional<std::tuple<T*, bool>> Add(int64_t timestamp) {
    has_unfreezed_ = true;
    bool become_freezed = false;
    if (data_.size() == 0) {
      AddEmtpyItem();
      // become freezed
      become_freezed = false;
    } else if (timestamp < data_.front().timestamp) {
      // incoming is out of date
      return std::nullopt;
    } else if (timestamp > data_.front().timestamp) {
      // come in, current is closed
      AddEmtpyItem();
      become_freezed = true;
    } else {
      // unfreezed update
      become_freezed = false;
    }

    // unfreezed update
    auto& item = data_.front();
    item.timestamp = timestamp;
    return std::optional(std::make_tuple(&item, become_freezed));
  }

  void Freeze() {
    has_unfreezed_ = false;
  }

  std::optional<const T*> GetUnfreezed() const {
    if (!has_unfreezed_) {
      return std::nullopt;
    }

    return std::optional(&data_.front());
  }

  // only iterate freeezed
  Iterator Begin() const {
    if (has_unfreezed_) {
      return std::next(data_.begin());
    }

    return data_.begin();
  }
  Iterator End() const { return data_.end(); }

  // only iterate freeezed
  ReverseIterator RBegin() const { return data_.rbegin(); }

  ReverseIterator REnd() const {
    if (has_unfreezed_) {
      return std::next(data_.rend(), -1);
    }

    return data_.rend();
  }

  uint32_t Size() const {
    if (data_.empty()) {
      return 0;
    } else if (has_unfreezed_) {
      return data_.size() - 1;
    }

    return data_.size();
  }

  uint32_t DeltaSize() const {
    if (delta_count_ == 0) {
      return 0;
    }

    if (has_unfreezed_) {
      return delta_count_ - 1;
    }

    return delta_count_;
  }

  Iterator DeltaBegin() const {
    if (delta_count_ == 0) {
      return data_.end();
    }

    if (has_unfreezed_) {
      return std::next(data_.begin());
    }

    return data_.begin();
  }

  Iterator DeltaEnd() const {
    if (delta_count_ == 0) {
      return data_.end();
    }

    return std::next(data_.begin(), delta_count_);
  }

  ReverseIterator DeltaRBegin() const {
    if (DeltaSize() == 0) {
      return data_.rend();
    }

    return std::next(data_.rbegin(), Size() - DeltaSize());
  }

  ReverseIterator DeltaREnd() const {
    if (DeltaSize() == 0) {
      return data_.rend();
    }

    if (has_unfreezed_) {
      return std::next(data_.rend(), -1);
    }

    return data_.rend();
  }

  void SnapshotClear() {
    data_.clear();
    delta_count_ = 0;
    option_ = SNAPSHOT;
  }

  void UpdateClear() {
    // no need clear, just keep same opration as snapshot
    option_ = DELTA;
  }

  TradingDataOption Type() const { return option_; }

 protected:
  void AddEmtpyItem() {
    data_.push_front({});
    data_.front().Clear();

    if (data_.size() > MAX_DATA_) {
      data_.resize(MAX_DATA_);
    }

    if (option_ == DELTA) {
      delta_count_++;
    }
  }

  std::deque<T> data_;
  bool has_unfreezed_ = false;
  int delta_count_ = 0;  // only added in the front
  TradingDataOption option_ = SNAPSHOT;
  fastfeed::proto::TradingDataInterval interval_;
  int64_t timestamp_;

  static constexpr int MAX_DATA_ = 2000;
};

inline std::ostream& operator<<(
    std::ostream& os,
    const TopLongShortAccountRatio& ratio) {
  os << "Top Long Short Account Ratio";
  os << "(timestamp:" << ratio.timestamp;
  os << ",long_short_ratio:";
  os << std::setw(10);
  os << ratio.long_short_ratio;
  os << ",long_account:";
  os << std::setw(10);
  os << ratio.long_account;
  os << ",short_account:";
  os << std::setw(10);
  os << ratio.short_account;
  os << ")";
  return os;
}

inline std::ostream& operator<<(
    std::ostream& os,
    const TopLongShortPositionRatio& ratio) {
  os << "Top Long Short Position Ratio";
  os << "(timestamp: " << ratio.timestamp;
  os << ",long_short_ratio:";
  os << std::setw(10);
  os << ratio.long_short_ratio;
  os << ",long_position:";
  os << std::setw(10);
  os << ratio.long_position;
  os << ",short_position:";
  os << std::setw(10);
  os << ratio.short_position;
  os << ")";
  return os;
}

inline std::ostream& operator<<(
    std::ostream& os,
    const GlobalLongShortAccountRatio& ratio) {
  os << "Global Long Short Account Ratio";
  os << "(timestamp: " << ratio.timestamp;
  os << ",long_short_ratio:";
  os << std::setw(10);
  os << ratio.long_short_ratio;
  os << ",long_account:";
  os << std::setw(10);
  os << ratio.long_account;
  os << ",short_account:";
  os << std::setw(10);
  os << ratio.short_account;
  os << ")";
  return os;
}

inline std::ostream& operator<<(
    std::ostream& os,
    const TakerBuySellRatio& ratio) {
  os << "Taker Long Short Ratio";
  os << "(timestamp: " << ratio.timestamp;
  os << ",buy_sell_ratio:";
  os << std::setw(10);
  os << ratio.buy_sell_ratio;
  os << ",buy_volume:";
  os << std::setw(10);
  os << ratio.buy_volume;
  os << ",sell_volume:";
  os << std::setw(10);
  os << ratio.sell_volume;
  os << ")";
  return os;
}
